using System;
using System.Collections.Generic;

namespace NAsserter.Impl
{
    /// <summary>
    /// This is used to perform assertions against arguments.  The purpose is to make assertion 
    /// </summary>
    /// <typeparam name="T">The type of the argument.</typeparam>
    public class ArgumentExpressionsImpl<T> : BaseArgumentExpression, IArgumentExpressions<T>
    {
        private readonly string _description;
        private readonly string _name;
        private readonly T _value;

        /// <summary>
        /// Creates a new instance of ArgumentExpressionsImpl.
        /// </summary>
        /// <param name="value">The argument value.</param>
        /// <param name="name">The name of the argument.</param>
        internal ArgumentExpressionsImpl(T value, string name)
        {
            _description = null;
            _name = name;
            _value = value;
        }

        /// <summary>
        /// Creates a new instance of ArgumentExpressionsImpl.
        /// </summary>
        /// <param name="value">The argument value.</param>
        /// <param name="name">The name of the argument.</param>
        internal ArgumentExpressionsImpl(T value, string name, string description)
        {
            _description = description;
            _name = name;
            _value = value;
        }

        /// <summary>
        /// The description of this argument.  This is usually just the name unless overriden by a nested expression.
        /// </summary>
        public virtual string Description { get { return _description ?? _name; } }

        /// <summary>
        /// The argument name.
        /// </summary>
        public string Name { get { return _name; } }

        /// <summary>
        /// The value of the argument that assertions will be made against.
        /// </summary>
        public T Value { get { return _value; } }

        /// <summary>
        /// Evaluate a Range expression.
        /// </summary>
        /// <returns>A nested ArgumentExpressionsImpl for method chaining.</returns>
        public IBetweenExpression<T> Between(T start)
        {
            return AssertThat.IsValid(_value) && AssertThat.IsValid(start) ? (IBetweenExpression<T>)new BetweenExpression<T>(this, start) : new NullBetweenExpression<T>(this, start);
        }

        /// <summary>
        /// Throws an exception if the argument value does not exist within the target args.
        /// </summary>
        /// <param name="args">The available values that the argument must exist within.</param>
        /// <exception cref="NotExistsArgumentException" />
        /// <returns>An root ArgumentExpressionsImpl for method chaining.</returns>
        public IArgumentExpressions<T> Exists(params T[] args)
        {
            return Exists((IEnumerable<T>) args);
        }

        /// <summary>
        /// Throws an exception if the argument value does not exist within the target args.
        /// </summary>
        /// <param name="existsInEnumerable">The available values that the argument must exist within.</param>
        /// <exception cref="NotExistsArgumentException" />
        /// <returns>An root ArgumentExpressionsImpl for method chaining.</returns>
        public IArgumentExpressions<T> Exists(IEnumerable<T> existsInEnumerable)
        {
            if (AssertThat.IsValid(existsInEnumerable) && AssertThat.IsValid(_value))
            {
                foreach (var o in existsInEnumerable)
                {
                    if (EqualityComparer<T>.Default.Equals(o, _value)) return this;
                }

                throw new NotExistsArgumentException(
                    string.Format(
                        "Argument ({0}) expected to be one of the following values: {1}", 
                        Description, 
                        AssertThat.ToString(existsInEnumerable)), 
                    Name);
            }

            return this;
        }

        /// <summary>
        /// Allows for method chaining of a boolean condition.
        /// </summary>
        /// <param name="resultFromExpression">A boolean which is a result of an expression.</param>
        /// <returns>An root ArgumentExpressionsImpl for method chaining.</returns>
        public IArgumentExpressions<T> If(bool resultFromExpression)
        {
            return resultFromExpression ? this : NullArgumentExpressionsImpl<T>.Instance;
        }

        /// <summary>
        /// Allows for method chaining of a boolean condition.
        /// </summary>
        /// <param name="condition">A predicate expressing a boolean condition.</param>
        /// <returns>An root ArgumentExpressionsImpl for method chaining.</returns>
        public IArgumentExpressions<T> If(Predicate<T> condition)
        {
            return condition.Invoke(_value) ? this : NullArgumentExpressionsImpl<T>.Instance;
        }

        public IArgumentExpressions<T> Is(T value)
        {
            if (!EqualityComparer<T>.Default.Equals(_value, value))
            {
                throw new ArgumentException(
                    string.Format("Argument ({0}) must be equal to {1}", Description, value), 
                    Name);
            }

            return this;
        }

        public IArgumentExpressions<T> IsNot(T value)
        {
            if (EqualityComparer<T>.Default.Equals(_value, value))
            {
                throw new ArgumentException(
                    string.Format("Argument ({0}) must be equal to {1}", Description, value),
                    Name);
            }

            return this;
        }

        /// <summary>
        /// Throws an exception if the argument value is its default value based on its type.
        /// </summary>
        /// <exception cref="ArgumentException" />
        /// <returns>An root ArgumentExpressionsImpl for method chaining.</returns>
        public IArgumentExpressions<T> IsNotDefault()
        {
            if (EqualityComparer<T>.Default.Equals(_value, default(T)))
            {
                throw new ArgumentException(
                    string.Format("Argument ({0}) may not be the default value for its type.", Description), 
                    Name);
            }

            return this;
        }

        /// <summary>
        /// Throws an exception if the argument value is a null value.
        /// </summary>
        /// <exception cref="ArgumentNullException" />
        /// <returns>An root ArgumentExpressionsImpl for method chaining.</returns>
        public IArgumentExpressions<T> IsNotNull()
        {
            if (_value == null)
            {
                throw new ArgumentNullException(
                    Name,
                    string.Format("Argument ({0}) was not expected to be null.", Description));
            }

            return this;
        }

        /// <summary>
        /// Throws an exception if the argument value is Null or an empty string..
        /// </summary>
        /// <exception cref="ArgumentException" />
        /// <returns>An root ArgumentExpressionsImpl for method chaining.</returns>
        public IArgumentExpressions<T> IsNotNullOrEmpty()
        {
            if (Equals(_value, null))
            {
                throw new ArgumentNullException(
                    Name,
                    string.Format("Argument ({0}) was not expected to be null.", Description));
            }

            var stringValue = _value as string;

            if (stringValue != null && stringValue.Length == 0)
            {
                throw new ArgumentException(
                    string.Format("Argument ({0}) an empty string.", Description), 
                    Name);
            }

            return this;
        }

        /// <summary>
        /// Throws an exception if condition does not evaluate to False.
        /// </summary>
        /// <param name="condition">Condition to be evaluated.</param>
        /// <exception cref="ArgumentException" />
        /// <returns>An root ArgumentExpressionsImpl for method chaining.</returns>
        public IArgumentExpressions<T> IsFalse(Predicate<T> condition)
        {
            if (condition.Invoke(_value))
            {
                throw new ArgumentException(
                    string.Format("Condition for argument ({0}) based on argument expected to be false", Description), 
                    Name);
            }

            return this;
        }

        /// <summary>
        /// Throws an exception if condition does not evaluate to True.
        /// </summary>
        /// <param name="condition">Condition to be evaluated.</param>
        /// <exception cref="ArgumentException" />
        /// <returns>An root ArgumentExpressionsImpl for method chaining.</returns>
        public IArgumentExpressions<T> IsTrue(Predicate<T> condition)
        {
            if (!condition.Invoke(_value))
            {
                throw new ArgumentException(
                    string.Format("Condition for argument {0} based on argument expected to be true", Description),
                    Name);
            }

            return this;
        }

        /// <summary>
        /// Allows for an expression to be evaluated against this arguments property.
        /// </summary>
        /// <typeparam name="TProp">The type of the target property's value.</typeparam>
        /// <param name="valueCallback">An expression returning the target property's value.</param>
        /// <param name="name">The name of the target property.</param>
        /// <returns>A nested ArgumentExpressionsImpl for method chaining.</returns>
        public PropertyArgumentExpression<T, TProp> Property<TProp>(Func<T, TProp> valueCallback, string name)
        {
            return new PropertyArgumentExpression<T, TProp>(this, valueCallback.Invoke(Value), name);
        }
    }
}